﻿using System;
using System.IO;
using System.Collections.Generic;
using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Types;
using System.Threading;

namespace NAntExt.Tasks
{
    [TaskName("parallel-join")]
    public class JoinParallelTask : Task
    {

        [TaskAttribute("contextKey")]
        [StringValidator(AllowEmpty = false)]
        public string ContextKey { get; set; }

        [TaskAttribute("maxJoinCount")]
        [Int32Validator()]
        public int MaximumJoinCount { get; set; }

        [TaskAttribute("minJoinCount")]
        [Int32Validator()]
        public int MinimumJoinCount { get; set; }

        /// <summary>
        /// Entry point for the task.
        /// </summary>
        protected override void ExecuteTask()
        {
            try
            {
                if (MaximumJoinCount < 1)
                {
                    MaximumJoinCount = int.MaxValue;
                }

                if (MinimumJoinCount < 1)
                {
                    MinimumJoinCount = 1;
                }

                if (string.IsNullOrEmpty(ContextKey))
                {
                    ContextKey = ParallelContext.DefaultContextKey;
                }

                var context = ParallelContext.GetContext(this.ContextKey);
                if (context == null)
                {
                    Log(Level.Error, "Error: No context exist for key {0}.", this.ContextKey);
                    return;
                }

                // do we have enough threads to join
                if (MinimumJoinCount > context.GetQueuedThreadCount())
                {
                    return;
                }

                // wait while child threads finish
                var joinCount = 0;
                while (context.GetQueuedThreadCount() > 0 && joinCount < MaximumJoinCount)
                {
                    // dequeue the next thread then join it to the current one
                    // this will block the current thread while it joins
                    ParallelTask thread = context.Dequeue();
                    while (!thread.CompletedEvent.WaitOne(3000))
                    {
                        Log(Level.Info, "PROGRESS: Elapsed Time: {0} Context: {1} Thread: {2} Queued Thread Count: {3}", thread.Timer.Elapsed, this.ContextKey, thread.Description, context.GetQueuedThreadCount());
                    }

                    // join to the thread
                    thread.Thread.Join();

                    // output something useful
                    Log(Level.Info, "FINISHED: Elapsed Time: {0} Context: {1} Thread: {2} Queued Thread Count: {3}", thread.Timer.Elapsed, this.ContextKey, thread.Description, context.GetQueuedThreadCount());

                    // save the error count because we're going to dequeue the errors so the
                    // count will change
                    int errorCount = thread.Exceptions.Count;

                    // see if there were errors
                    while (thread.Exceptions.Count > 0)
                    {
                        Exception thrownEx = thread.Exceptions.Dequeue();
                        string errorMessage = (this.Verbose) ? thrownEx.ToString() : thrownEx.Message;
                        Log(Level.Error, "ERROR: A parallel thread threw an unhandled exception. Exception = {0}", errorMessage);
                    }

                    // error out if we have errors
                    if (errorCount > 0)
                    {
                        throw new System.ApplicationException(string.Format("Errors occurred during the processing of parallel tasks. Approximately {0} error(s) occurred. Review log for details.", errorCount));
                    }

                    // increment the join count
                    joinCount++;
                }

            }
            catch (Exception ex)
            {
                this.ThrowOrLog(this.Location, ex);
            }
        }

    }
}
